Controls

Plan Your Migration to the Visual Studio 2005 Navigation Controls

Dave Donaldson and Steven DeWalt

This article is based on the March 2004 Community Technology Preview of ASP.NET 2.0. All information contained herein is subject to change.

This article discusses:

  • The SiteMapDataSource and SiteMapPath controls
  • The TreeView control
  • A breadcrumb navigation control for ASP.NET 1.1
This article uses the following technologies:
ASP.NET and Visual Basic .NET

Code download available at:NavigationControls.exe(145 KB)

A Simple Web Site
The Web.sitemap File
The SiteMapPath Control
The SiteMapDataSource Control
The TreeView Control
A Breadcrumb Control Today
The Breadcrumb Class
Conclusion

When building the UI for a Web app, one of the first items of business is designing the overall structure of the Web site, which includes all site navigation. Navigation controls are typically placed on the left or along the top of Web pages and might include the increasingly popular breadcrumb navigation (which shows the path the user has walked through), creating an overall menu system. To implement navigation, there are several technologies available, but they are often too labor-intensive for developers. Examples are DHTML, include files (for those using classic ASP), and user controls in ASP.NET. Sometimes navigation elements are hardcoded, but more often than not, navigation items are kept in a data store for read-only access at run time, whether in a database (and cached) or in XML configuration files.

In ASP.NET 2.0, most of the work required to implement site navigation is built in so you can take advantage of the rich feature set without having to write a single line of server-side code.

A Simple Web Site

To demonstrate how easily you can build a Web site using the ASP.NET 2.0 navigation controls, we've created a fictional, jack-of-all-trades computer company: Northwind Traders. The company's Web site has a menu on the left and a breadcrumb at the top-right. Looking at the design of a page in the Visual Studio® 2005 designer, shown in Figure 1, you can see that three controls are circled. Circled in blue is the SiteMapPath control, which effectively displays the common breadcrumb navigation. Circled in red is the TreeView control, currently set to display a flat view and not to expand. Circled in green is the SiteMapDataSource control, which acts as the data source for the TreeView control. Keep this picture in mind as we walk you through the new navigation controls, their properties, and other items of interest.

Figure 1** A Page in the Visual Studio 2005 Designer **

 

The Web.sitemap File

The key to the new navigation controls is the new web.sitemap file, shown in Figure 2. This is a standard XML file that acts as the default data store for the navigation items. When creating a new Web site in Visual Studio 2005, the web.sitemap file is not added by default so you have to add it yourself manually (this will most likely be changed before final release). To add it, right-click on the Web site and select Add New Item. Under Standard Templates, select XML File and rename it to web.sitemap (the final release will support adding a sitemap file as a unique type).

Once you have created the file, you need to complete it according to the schema for web.sitemap (see Figure 3), which is not at all difficult or cumbersome. Basically, you need a root element () and zero or more nested elements. Note that the structure of the elements is logical and does not have to map to the physical directory structure of your Web site. For example, the files for the Northwind Traders Web site are all located in the root directory. Another thing to keep in mind is that for each element, the URL attribute must be unique within the sitemap file. Also note that in the March 2004 Technical Preview build of Visual Studio 2005, there is no IntelliSense® to work from when filling out your web.sitemap file.

To make the web.sitemap file available for consumption by the navigation controls, a default site map provider is available. In the machine.config file installed Visual Studio 2005, the default site map provider is named AspNetXmlSiteMapProvider and uses the class type System.Web.XmlSiteMapProvider. The provider has an attribute that determines the default site map file. This attribute, siteMapFile, equals "web.sitemap". A new abstract base class named SiteMapProvider, from which XmlSiteMapProvider derives, has been added to the next version of the Microsoft® .NET Framework, which allows you to implement your own site map providers.

You should note that it's possible that the name of the web.sitemap file could change before Visual Studio 2005 is released. For instance, in keeping with the .config naming convention, the file might end up being called sitemap.config.

The SiteMapPath Control

Before we get into the details of this control, let's take some time to understand the importance of a breadcrumb trail in a Web site. In medium to large Web sites, it's very easy for users to quickly get lost in the maze of links, and it's often hard for users to find their way back to the beginning of their trail. This is where the breadcrumb is so important to the user experience. Breadcrumb navigation allows users to understand quickly where they are within the site without having to go back to the home page and start over.

To create this type of navigation in the next version of Visual Studio, you use the SiteMapPath control. By default, this control uses the web.sitemap file as its data store so once you've created that file and filled it in, you're already halfway finished implementing breadcrumb navigation on your Web site.

Located under the Navigation tab of the designer toolbox, the SiteMapPath control is available to drag and drop onto your Web form in the designer. Once you've done that, you'll have a basic breadcrumb trail in your Web site based on the elements in your web.sitemap file. Chances are you'll want to adjust the properties of the SiteMapPath control to fit the overall design of your site. There are too many properties of the SiteMapPath control to cover them all in depth here, but we'll describe a handful of properties you should become familiar with right away.

PathDirection The default is RootToCurrent, which displays the breadcrumb in this fashion: Home > Products > Software. The other option is CurrentToRoot, which reverses the order: Software > Products > Home.

PathSeparator This is whatever text you want to display between the nodes of the breadcrumb. Don't forget to include the leading and trailing space. For instance, to display "Home > Products > Software", you would set this property to " > ", not ">".

RenderCurrentNodeAsLink This basically determines whether or not to display the current page you are on as a link to itself in the breadcrumb. The default is False.

Each item in this next list is a set of properties to define all font and style items as well as general appearance. Each set of properties contains the same property subset.

CurrentNodeStyle Gives the current node in the breadcrumb its appearance (overrides NodeStyle).

HoverNodeStyle Determines the appearance of a breadcrumb node when you hover over it.

NodeStyle Gives all nodes in the breadcrumb a consistent style when the breadcrumb is displayed (unless it is overridden by RootNodeStyle or CurrentNodeStyle).

PathSeparatorStyle Sets the appearance for whatever text you defined in PathSeparator.

RootNodeStyle Determines the style of the root node of the breadcrumb (overrides NodeStyle).

As you can see, you have complete control over how you want your breadcrumb to behave and look, and it's all provided for you without writing a single line of server-side code. For instance, if you want users to quickly recognize the current node in the breadcrumb of your Web site, you can set the font bold property to True under CurrentNodeStyle, or maybe set the font size to Large, while at the same time, the font bold property under NodeStyle can be set to False and its font size set to Small. A quick look at the source of the home page for the Northwind Traders Web site shows how we implemented the SiteMapPath control:

The SiteMapPath control also has a property named SiteMapProvider. When left blank (the default), the control uses the AspNetXmlSiteMapProvider mentioned earlier (or XmlSiteMapProvider), which in turn reads the web.sitemap file. If you created your own site map provider and added the entry in machine.config or web.config, this is the property you use to make that change.

The SiteMapDataSource Control

This control is very important when you need to view hierarchical data, such as common navigational structures. A SiteMapDataSource control will often be used as the data source for other controls to bind to, such as the TreeView or DropDownList controls. To use a SiteMapDataSource control, find it under the Data tab of the designer toolbox and drag it onto your Web form.

Figure 4** SiteMapDataSource Properties **

If you take a look at Figure 4, you'll see that the property list for a SiteMapDataSource control is not very extensive, so let's go into some detail about each of its properties.

EnableViewState If you're already using ASP.NET, this property will be familiar. It is used to turn ViewState on or off. The value is set to True by default.

FlatDepth This property specifies how deep in the hierarchical site map to retrieve nodes from (provided by the SiteMapProvider, which by default is the web.sitemap file). The default value is -1, which indicates there is no limit to the depth. A value of 0 corresponds to the root node level of the hierarchy, while a positive integer corresponds to that many levels below the root node. This is useful if you have a deep hierarchy and want to limit the display to a set number of levels.

ParentLevelsDisplayed Specifies the number of levels of parent nodes to retrieve, relative to the current node. The default is -1, which means there is no limit of parent levels retrieved.

SiteMapProvider Determines the site map provider for the control to bind to. The default is blank, which means the control uses the default site map provider discussed earlier. If you want to use your own site map provider instead of the default, you would type it in here.

SiteMapViewType Determines how the data source is viewed. The default is Tree, which depicts data in the same hierarchical structure that the site map is specified in. The other values are Flat and Path. Flat shows only a flat view of the nodes with no hierarchical structure while Path displays data as a hierarchical path between the current node and root node, in the same way a SiteMapPath control renders its site map data.

StartingDepth Specifies the depth at which the control begins retrieving elements from the web.sitemap file. The default value is -1, which indicates that there is no restriction on the depth that the data source retrieves nodes from. A value of 0 is used to indicate the root node.

StartingNodeType By default, this property is set to Root, which means the starting node is the root node of the hierarchy, but you can set the starting node to be any node. The starting point can be set to a node relative to the current position in the site map by setting this property to Parent or Current. If you use Parent, the control always begins from the parent node of the currently displayed page, unless the currently displayed page corresponds to the root node. If this is set to Current, the control always begins from the node that represents the currently displayed page.

StartingNodeUrl Instead of setting the starting node to the Root, Current, or Parent node, you can specify the URL of an element in the web.sitemap file as the starting point. Setting this property takes precedence over both the StartingDepth and StartingNodeType properties.

ID Note what the ID of the control is when using it as the data source for other controls, such as the TreeView or DropDownList controls.

A typical declaration of the SiteMapDataSource control on the Northwind Traders Web site, which uses the default values for all properties except for SiteMapViewType, looks like this: There's no limit to the number of SiteMapDataSource controls you can have. For instance, you can have a TreeView control on the left side of Web pages that binds to one SiteMapDataSource control and flat navigation on the right side that binds to another SiteMapDataSource control.

The TreeView Control

For those of you who have developed Windows®-based applications using Windows Forms, a TreeView control is a familiar item; however, using TreeViews has not been common practice in the realm of Web applications. To implement something similar to a TreeView in a Web site today, you typically have to write a large amount of DHTML on the client to get it to work properly. This proves to be very painful because you have to account for all the idiosyncrasies among the various Web browsers and their versions, and testing becomes a nightmare. All that complexity has gone away in ASP.NET 2.0, encapsulated in the new TreeView control.

The easiest way to use the TreeView control for site navigation is to drag the control from the designer toolbox (located under the Core tab) and bind its data source to a SiteMapDataSource control. Once you've done that, the rest of the implementation involves setting the properties, all the while not having to write any server-side code. Just as with the SiteMapPath control, there are more properties than we can cover in this article, but Figure 5 lists the main ones. We'll describe a handful of them in more depth here.

Figure 5** TreeView Properties **

ImageSet This defines a set of images to use in your TreeView control. There are numerous image set styles provided, most notable are Custom, MSDN, XPFileExplorer, and Windows_Help. Picking one of the numerous predefined sets allows your TreeView to use familiar images and inherit those styles.

ShowExpandCollapse At first, this might seem a bit odd because we're so used to seeing TreeViews with the expand/collapse indicators (the + and - signs); however, this property gives you the option of not showing those indicators. The default, however, is True.

ShowLines This property determines whether or not to display the lines connecting child nodes in the tree to their parent node. The default value is False.

EnableClientScript This is a fairly important property, especially if you want your TreeView control to expand and collapse, which will usually be the case (although we chose not to do that in the Northwind Traders Web site). This property indicates whether or not to render client-side script to handle the events for expanding and collapsing nodes, which it does by default. When this is set to True, expensive trips to the server are avoided, but if you set this property to False, a postback to the server is required every time a user clicks a node in the tree.

ExpandDepth Sets the number of expanded tree levels when the TreeView control is first displayed. For example, setting this property to 2 expands the root node and all parent nodes immediately under the root node. The default is -1, which indicates that all nodes are fully expanded.

MaxDataBindDepth When binding this control to a data source, such as a SiteMapDataSource control, use this property to limit the number of tree levels to bind to the control. For example, setting this property to 2 will bind only the root node and any nodes immediately under the root node to the TreeView control. All remaining nodes in the data source (the SiteMapDataSource control) are ignored, which could be handy in a deep hierarchical site map. The default is -1, which binds all of the tree levels in the data source to the control.

PopulateNodesFromClient By default this property is set to True, which means the TreeView is statically predefined by what's in the web.sitemap file. Because of this, round-trips to the server are eliminated. In order for this property to be True, the EnableClientScript property must also be set to True; otherwise, postbacks to the server will take place.

DataSourceID This is the ID for the data source to bind to. If you've created a SiteMapDataSource control and want to use it as the data source for your TreeView, type the ID of the SiteMapDataSource control here.

There are many more properties for TreeView controls than those detailed here, including the same node style properties (and subproperties) found in the SiteMapPath control, thus allowing you very fine-grained control over how you want your TreeView to behave and appear. We'll leave the rest of the exploration of the TreeView properties up to you. Figure 6 shows the source of one of the Web pages in the Northwind Traders Web site, which gives you an idea of how we implemented the TreeView control.

A Breadcrumb Control Today

Now you're probably thinking: these controls are all well and good, and I can't wait to use them, but I want this functionality right now. Well, we've taken the liberty of creating a simple breadcrumb control using ASP.NET 1.1, basing its design loosely on the Visual Studio 2005 SiteMapPath control. By following the design of this control, we've created an easy migration path when the time comes to switch from this control to the new one. Naturally, we called the control Breadcrumb.

For the Breadcrumb control, we thought it would be a good idea to keep the web.sitemap file (and its internal structure) as the default data source loaded from the root of the Web site. This sample should give you a general idea about how to roll your own control, but the actual APIs and implementations in the final release of ASP.NET 2.0 could be different.

Because the web.sitemap file can contain any number of non-nested and nested elements, we created the SiteMapNode class to hold the attributes (properties) for each one, such as Title, Url, and Description. Since each element can contain other elements, we added a property in the class called NodeList, which is a collection of other SiteMapNodes. Figure 7 contains the code for this class.

Namespace DoughLibrary Public Class SiteMapNode Private m_strUrl As String = String.Empty Private m_strTitle As String = String.Empty Private m_strDescription As String = String.Empty Private m_blnHasLeaf As Boolean = False Private m_objNodeList As SiteMapNodeList Public Sub New() Me.New(False) End Sub Public Sub New(ByVal hasLeaf As Boolean, ByVal url As String, _ ByVal title As String, ByVal description As _ String) Me.New(hasLeaf) Me.m_strUrl = url Me.m_strTitle = title Me.m_strDescription = description End Sub Public Sub New(ByVal hasLeaf As Boolean) If (hasLeaf = True) Then Me.m_blnHasLeaf = True Me.m_objNodeList = New SiteMapNodeList Else Me.m_blnHasLeaf = False End If End Sub Public Property HasLeaf() As Boolean Get Return Me.m_blnHasLeaf End Get Set(ByVal Value As Boolean) Me.m_blnHasLeaf = Value End Set End Property Public Property NodeList() As SiteMapNodeList Get Return Me.m_objNodeList End Get Set(ByVal Value As SiteMapNodeList) Me.m_objNodeList = Value End Set End Property Public Property Url() As String Get Return Me.m_strUrl End Get Set(ByVal Value As String) Me.m_strUrl = Value End Set End Property Public Property Title() As String Get Return Me.m_strTitle End Get Set(ByVal Value As String) Me.m_strTitle = Value End Set End Property Public Property Description() As String Get Return Me.m_strDescription End Get Set(ByVal Value As String) Me.m_strDescription = Value End Set End Property End Class End Namespace

Figure 8 displays the code for the SiteMapNodeList class, which is your basic collection that inherits from ArrayList, but it is strongly typed to contain only SiteMapNodes. The SiteMapNodeList class is constructed when a SiteMapNode is determined to contain other SiteMapNodes and is populated by simply adding some SiteMapNode objects to it.

Namespace DoughLibrary Public Class SiteMapNodeList : Inherits ArrayList Default Public Shadows Property Item(ByVal index As Integer) _ As SiteMapNode Get Return CType(MyBase.Item(index), SiteMapNode) End Get Set(ByVal Value As SiteMapNode) MyBase.Item(index) = Value End Set End Property Public Shadows Function Add(ByVal value As SiteMapNode) _ As Integer MyBase.Add(value) End Function End Class End Namespace

Similar to SiteMapNodeList, the SiteMapPath class is a strongly typed collection of SiteMapNode objects and is used by the Breadcrumb control to obtain the actual nodes to render to the browser (in fact, the code is identical to the SiteMapNodeList class except for the name of the class itself). For example, if the path to display to the user should be Home > Services > Training, the SiteMapPath object would contain the Home SiteMapNode, the Services SiteMapNode, and the Training SiteMapNode.

Note that the order of the SiteMapPath collection is always root node to current node. Because of this, the Breadcrumb control can easily determine the direction in which to render the nodes in the browser. If the PathDirection property is set to RootToCurrent the control reads the SiteMapPath collection from first element to last element, and if the PathDirection property is set to CurrentToRoot the control reads the collection from last element to first element.

The SiteMap class is where all the magic happens; it takes care of loading all nodes from the web.sitemap file and builds the SiteMapPath object, which is then exposed as a read-only property (see Figure 9). Because we know that the Breadcrumb control only cares about rendering a navigation path, providing the SiteMapPath property makes sense because, in essence, the SiteMapPath is the only thing the control cares about. Note, too, that our implementation has a hardcoded dependency on the shape of the sitemap file shown in Figure 2. For robustness, the GetSiteMapPath method should instead use recursion.

Imports System.Xml Namespace DoughLibrary Public Class SiteMap Private m_objSiteMapNode As SiteMapNode Private m_objSiteMapPath As SiteMapPath Private m_uriSiteMapFile As Uri Private m_uriCurrentUrl As Uri Private m_objXmlReader As XmlTextReader Private m_intNodeDepth As Integer = 0 Public Sub New(ByVal siteMapFile As Uri, ByVal currentUrl As Uri) Me.m_uriSiteMapFile = siteMapFile Me.m_uriCurrentUrl = currentUrl Me.m_objSiteMapNode = New SiteMapNode(True) Me.LoadSiteMapNodes() Me.m_objSiteMapPath = GetSiteMapPath() End Sub Public ReadOnly Property SiteMapPath() As SiteMapPath Get Return Me.m_objSiteMapPath End Get End Property Private ReadOnly Property CurrentUrl() As Uri Get Return Me.m_uriCurrentUrl End Get End Property Private ReadOnly Property SiteMapFile() As Uri Get Return Me.m_uriSiteMapFile End Get End Property Private Property Depth() As Integer Get Return Me.m_intNodeDepth End Get Set(ByVal Value As Integer) Me.m_intNodeDepth = Value End Set End Property Private Sub LoadSiteMapNodes() Me.m_objXmlReader = New XmlTextReader(SiteMapFile.ToString()) Me.LoadSiteMapXmlFile() End Sub Private Sub LoadSiteMapXmlFile() ' Ignore whitespace in web.sitemap file Me.m_objXmlReader.WhitespaceHandling = WhitespaceHandling.None ' Skip xml declaration and any xml prolog Me.m_objXmlReader.MoveToContent() If (Me.m_objXmlReader.IsStartElement("siteMap") = True) Then ' Read past document root element Me.m_objXmlReader.ReadStartElement("siteMap") ReadSiteMapNode() End If End Sub Private Sub ReadSiteMapNode() ' Temp branch and leaf nodes for tree traversal Dim objBranch As SiteMapNode Dim objLeaf As SiteMapNode ' Read the root node's attributes ReadNodeAttribs(Me.m_objSiteMapNode) ' Cycle through the web.sitemap file nodes While (Me.m_objXmlReader.Read()) If (Me.m_objXmlReader.NodeType = XmlNodeType.Element) Then If (Me.m_objXmlReader.IsEmptyElement() = False) Then Me.Depth += 1 ' On a branch node, so create NodeList for leaves objBranch = New SiteMapNode(True) ReadNodeAttribs(objBranch) Me.m_objSiteMapNode.NodeList.Add(objBranch) Else ' At leaf, so don't create NodeList objLeaf = New SiteMapNode(False) objBranch.NodeList.Add(objLeaf) ReadNodeAttribs(objLeaf) End If Else If (Me.m_objXmlReader.NodeType = _ XmlNodeType.EndElement) Then Me.Depth -= 1 End If End If End While End Sub Private Sub ReadNodeAttribs(ByRef node As SiteMapNode) ' Read title, description, and url attributes for the passed ' node node.Title = Me.m_objXmlReader.GetAttribute("title") node.Description = _ Me.m_objXmlReader.GetAttribute("description") node.Url = Me.m_objXmlReader.GetAttribute("url") End Sub Private Function GetSiteMapPath() As SiteMapPath Dim objPath As SiteMapPath = New SiteMapPath Dim objCurrentNode As SiteMapNode = GetCurrentNode() Dim objTempNode As SiteMapNode = New SiteMapNode Dim i As Integer = 0 Dim blnFound As Boolean = False ' Always add the root node objPath.Add(Me.m_objSiteMapNode) If Not (objCurrentNode Is Me.m_objSiteMapNode) Then ' This loop takes advantage of knowing that the web.sitemap ' file is 3 levels deep. For robustness, this loop should ' make use of recursion instead. While Not blnFound objTempNode = Me.m_objSiteMapNode.NodeList.Item(i) If (objTempNode Is objCurrentNode) Then objPath.Add(objTempNode) blnFound = True Else If (objTempNode.HasLeaf) Then For Each objNode As SiteMapNode In _ objTempNode.NodeList If (objNode Is objCurrentNode) Then objPath.Add(objTempNode) objPath.Add(objNode) blnFound = True Exit For End If Next End If End If i += 1 End While End If Return objPath End Function Private Function GetCurrentNode() As SiteMapNode Dim objResultNode As SiteMapNode = New SiteMapNode Dim objTempNode As SiteMapNode = New SiteMapNode Dim i As Integer = 0 Dim blnFound As Boolean = False If (Me.m_objSiteMapNode.Url.Equals( _ Me.CurrentUrl.PathAndQuery)) Then objResultNode = Me.m_objSiteMapNode Else ' This loop takes advantage of knowing that the web.sitemap ' file is 3 levels deep. For robustness, this loop should ' make use of recursion instead. While Not blnFound objTempNode = Me.m_objSiteMapNode.NodeList.Item(i) If (objTempNode.Url.Equals( _ Me.CurrentUrl.PathAndQuery)) Then objResultNode = objTempNode blnFound = True Else If (objTempNode.HasLeaf) Then For Each objNode As SiteMapNode In _ objTempNode.NodeList If _ (objNode.Url.Equals( _ Me.CurrentUrl.PathAndQuery)) Then objResultNode = objNode blnFound = True Exit For End If Next End If End If i += 1 End While End If Return objResultNode End Function End Class End Namespace

As the control is loaded, it creates a new instance of the SiteMap class by passing it the current URL and the name of the site map file. All work in the SiteMap class is done in the constructor, first by loading all elements into their own SiteMapNode objects (and linking them together) and then by building the SiteMapPath object. Once the control has instantiated a SiteMap, it can just ask for the SiteMapPath property to use for rendering to the browser. The code for doing this is very short: Dim uriFile As Uri = New Uri("https://localhost/nwt/web.sitemap") Dim uriUrl As Uri = New Uri("https://localhost/nwt/products.aspx") Dim objSiteMap As SiteMap = New SiteMap(uriFile, uriUrl) Dim objPath As SiteMapPath = objSiteMap.SiteMapPath

The loading of all the SiteMapNodes is done with help from the XmlTextReader, which is used by a few private helper methods. By checking if each in the web.sitemap file is an empty element, we can determine which nodes are branches and which ones are leaves (where branches equate to nested nodes and leaves are non-nested nodes). If a node is determined to be a branch, it's added to the NodeList of the parent node, but if the node is a leaf it's added to the NodeList of the current branch. This also allows us to capture the depth of the tree.

Once all SiteMapNodes are loaded, the SiteMapPath is built, which is done by first determining which SiteMapNode is the current node. Like the Visual Studio 2005 version of the breadcrumb implementation, uniqueness is guaranteed by the given URL. This is compared to the URL property of each SiteMapNode while looping through them until a match is found, after which the loop is exited. Now that the current SiteMapNode has been found, we loop through all nodes again to track the path, always from root node to current node, as shown in the GetSiteMapPath method.

The Breadcrumb Class

All server-side code for the Breadcrumb control is found in its custom control class and handles the loading of the web.sitemap file (OnLoad) and the rendering of the HTML (Render). It exposes an enumeration, PathDirection, which has the following values: RootToCurrent (the default) and CurrentToRoot. Also available are the properties listed in Figure 10, whose names are the same as in ASP.NET 2.0. Note that each of the "style" properties are named like their ASP.NET 2.0 cousins, but they behave slightly differently. Instead of being a set of properties, each style is a discrete property that can be set via the stylesheet.

Property Description
PathDirection Same as the ASP.NET 2.0 version
PathSeparator Same as the ASP.NET 2.0 version
RenderCurrentNodeAsLink Same as the ASP.NET 2.0 version
CurrentNodeStyle Gives the current node in the breadcrumb its appearance (overrides NodeStyle)
HoverNodeStyle Determines the appearance of a breadcrumb node when you hover over it
NodeStyle Gives all nodes in the breadcrumb a consistent style when the breadcrumb is displayed
PathSeparatorStyle Sets the appearance for any text you defined in the PathSeparator property
RootNodeStyle Determines the style of the root node of the breadcrumb (overrides NodeStyle)

During the control's load event, an instance of SiteMap is created and the control then gets the SiteMapPath as just described. Next, the overriding Render method outputs the navigation path to the browser in the proper PathDirection with all associated styles to complete its appearance. Figure 11 shows the code for this class, and the code snippet that follows demonstrates how to use this custom control on a Web form. Take a look at the similarities between this code and the code that is necessary to use the ASP.NET 2.0 SiteMapPath control:

Imports System.ComponentModel Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Web Namespace DoughLibrary Public Enum PathDirection RootToCurrent CurrentToRoot End Enum Public Class Breadcrumb : Inherits WebControl Private m_enmPathDirection As PathDirection = _ PathDirection.RootToCurrent Private m_strPathSeparator As String = String.Empty Private m_blnRenderCurrentNodeAsLink As Boolean = False Private m_strCurrentNodeStyle As String = String.Empty Private m_strHoverNodeStyle As String = String.Empty Private m_strNodeStyle As String = String.Empty Private m_strPathSeparatorStyle As String = String.Empty Private m_strRootNodeStyle As String = String.Empty Private m_SiteMapPath As SiteMapPath Private Property SiteMapPath() As SiteMapPath Get Return Me.m_SiteMapPath End Get Set(ByVal Value As SiteMapPath) Me.m_SiteMapPath = Value End Set End Property Public Property PathDirection() As PathDirection Get Return Me.m_enmPathDirection End Get Set(ByVal Value As PathDirection) Me.m_enmPathDirection = Value End Set End Property Public Property PathSeparator() As String Get Return Me.m_strPathSeparator End Get Set(ByVal Value As String) Me.m_strPathSeparator = Value End Set End Property Public Property RenderCurrentNodeAsLink() As Boolean Get Return Me.m_blnRenderCurrentNodeAsLink End Get Set(ByVal Value As Boolean) Me.m_blnRenderCurrentNodeAsLink = Value End Set End Property Public Property CurrentNodeStyle() As String Get Return Me.m_strCurrentNodeStyle End Get Set(ByVal Value As String) Me.m_strCurrentNodeStyle = Value End Set End Property 'Other properties removed for brevity Public Property PathSeparatorStyle() As String Get Return Me.m_strPathSeparatorStyle End Get Set(ByVal Value As String) Me.m_strPathSeparatorStyle = Value End Set End Property Public Property RootNodeStyle() As String Get Return Me.m_strRootNodeStyle End Get Set(ByVal Value As String) Me.m_strRootNodeStyle = Value End Set End Property Protected Overrides Sub OnLoad(ByVal e As System.EventArgs) Dim strFile As String = Page.Request.MapPath("web.sitemap") Dim strPage As String = Page.Request.Url.ToString.ToLower Dim objSiteMap As SiteMap = New SiteMap(New Uri(strFile), _ New Uri(strPage)) Me.m_SiteMapPath = objSiteMap.SiteMapPath End Sub Protected Overrides Sub Render(ByVal output As _ System.Web.UI.HtmlTextWriter) ' SiteMap Class always returns the SiteMapPath in RootToCurrent ' Order. If PathDirection attribute is set to CurrentToRoot, ' reverse the SiteMapPath direction before rendering. If Me.PathDirection = PathDirection.CurrentToRoot Then Me.SiteMapPath.Reverse() End If ' outermost span start element for breadcrumb control output.AddAttribute(HtmlTextWriterAttribute.Id, Me.ID) output.RenderBeginTag(HtmlTextWriterTag.Span) For Each objCrumb As SiteMapNode In Me.SiteMapPath ' start crumb span output.RenderBeginTag(HtmlTextWriterTag.Span) ' start crumb hyperlink anchor If Me.SiteMapPath.IndexOf(objCrumb) = 0 Then If Me.PathDirection = PathDirection.RootToCurrent Then ' set css of 1st crumb: root if RootToCurrent ' direction output.AddAttribute( _ HtmlTextWriterAttribute.Class, Me.RootNodeStyle) Else ' set css of 1st crumb: current if CurrentToRoot ' direction output.AddAttribute( _ HtmlTextWriterAttribute.Class, Me.CurrentNodeStyle) End If ElseIf Me.SiteMapPath.IndexOf(objCrumb) = _ (Me.SiteMapPath.Count - 1) Then If Me.PathDirection = PathDirection.RootToCurrent Then ' set css of last crumb: current if RootToCurrent ' direction output.AddAttribute(HtmlTextWriterAttribute.Class, _ Me.CurrentNodeStyle) Else ' set css of last crumb: root if CurrentToRoot ' direction output.AddAttribute(HtmlTextWriterAttribute.Class, _ Me.RootNodeStyle) End If Else ' set css of all other crumbs to the NodeStyle property ' value output.AddAttribute(HtmlTextWriterAttribute.Class, _ Me.NodeStyle) End If If RenderCurrentNodeAsLink = False Then If ((Me.PathDirection = PathDirection.RootToCurrent) _ AndAlso (Me.SiteMapPath.IndexOf(objCrumb) = _ (Me.SiteMapPath.Count - 1))) Or _ ((Me.PathDirection = _ PathDirection.CurrentToRoot) AndAlso _ (Me.SiteMapPath.IndexOf(objCrumb) = 0)) Then output.RenderBeginTag(HtmlTextWriterTag.Span) ' output.Write(objCrumb.Title) output.RenderEndTag() 'Else output.AddAttribute(HtmlTextWriterAttribute.Title, _ objCrumb.Description) output.AddAttribute(HtmlTextWriterAttribute.Href, _ objCrumb.Url) output.RenderBeginTag(HtmlTextWriterTag.A) ' output.Write(objCrumb.Title) output.RenderEndTag() ' End If Else output.AddAttribute(HtmlTextWriterAttribute.Title, _ objCrumb.Description) output.AddAttribute(HtmlTextWriterAttribute.Href, _ objCrumb.Url) output.RenderBeginTag(HtmlTextWriterTag.A) ' output.Write(objCrumb.Title) output.RenderEndTag() ' End If output.RenderEndTag() ' (end crumb span) ' append path separator if not on last crumb If (Me.SiteMapPath.IndexOf(objCrumb) _ < (Me.SiteMapPath.Count - 1)) Then output.AddAttribute(HtmlTextWriterAttribute.Class, _ Me.PathSeparatorStyle) output.RenderBeginTag(HtmlTextWriterTag.Span) output.Write(Me.PathSeparator) output.RenderEndTag() ' End If Next output.RenderEndTag() ' (outermost span) End Sub End Class End Namespace

All the code we've described for the Breadcrumb control provides you a simple way to add navigation paths to your Web site. And even though there is room to increase its robustness (through caching, error handling, and recursion), the control is designed and written for easy extensibility.

Conclusion

As you can see, implementing a SiteMapPath (breadcrumb) control today that has some of the functionality provided by its ASP.NET 2.0 counterpart took several hundred lines of code. To implement a TreeView control today that's as powerful as the ASP.NET 2.0 version would take a significant amount of time and thousands of lines of code. That alone should have you clamoring for the new navigation controls coming in ASP.NET 2.0 because all that functionality and more is provided to you out of the box with minimal configuration and, more importantly, without the need to write the code yourself. With ASP.NET 2.0 in your arsenal you'll achieve navigational goals you didn't think possible.

Dave Donaldson and Steven DeWalt are both consulting IT architects for a large insurance company. When not working for the man, they can be found building .NET-based applications and services for their small software shop, LoudCarrot Software. You can contact them at https://www.loudcarrot.com.